8.2.3 基准测试

Go的 testing 包支持两种类型的测试,一种是用于检验程序功能性的功能测试(functional testing),而另一种则是用于查明任务单元性能的基准测试(benchmarking)。在上一节学习过如何进行功能测试之后,这一节我们将要学习如何进行基准测试。

跟单元测试一样,基准测试用例也需要放置到以 _test.go 为后缀的文件中,并且每个基准测试函数都需要符合以下格式:

func BenchmarkXxx(*testing.B) { ... }

作为例子,代码清单 8-5 展示了一个基准测试用例函数,这个函数定义在文件 bench_test.go 里面。

代码清单8-5 基准测试

package main
import (
 "testing"
)
// benchmarking the decode function
func BenchmarkDecode(b *testing.B) {
 for i := 0; i < b.N; i++ {  ❶
  decode("post.json")
 }
}

❶ 循环执行解码函数,以便对其进行b.N 次基准测试

正如代码所示,在Go语言中进行基准测试是非常直观的:测试程序要做的就是将被测试的代码执行 b.N 次,以便准确地检测出代码的响应时间,其中 b.N 的值将根据被执行的代码而改变。比如,在上面展示的基准测试例子中,测试程序就将 decode 函数执行了 b.N 次。

为了运行基准测试用例,用户需要在执行 go test 命令时使用基准测试标志 -bench ,并将一个正则表达式用作该标志的参数,从而标识出自己想要运行的基准测试文件。当我们需要运行目录下的所有基准测试文件时,只需要把点( .) 用作 -bench 标志的参数即可:

go test -v -cover -short –bench .

下面是这条命令的执行结果:

=== RUN TestDecode
--- PASS: TestDecode (0.00s)
=== RUN TestEncode
--- SKIP: TestEncode (0.00s)
main_test.go:38: Skipping encoding for now
=== RUN TestLongRunningTest
--- SKIP: TestLongRunningTest (0.00s)
main_test.go:44: Skipping long-running test in short mode
PASS
BenchmarkDecode 100000   19480 ns/op
coverage: 42.4% of statements
ok  unit_testing 2.243s

结果中的 100000 为测试时 b.N 的实际值,也就是函数被循环执行的次数。在这个例子中,迭代进行了10万次,并且每次耗费了19480 ns,即0.01948 ms。需要注意的是,在进行基准测试时,测试用例的迭代次数是由Go自行决定的,虽然用户可以通过限制基准测试的运行时间达到限制迭代次数的目的,但用户是无法直接指定迭代次数的——测试程序将进行足够多次的迭代,直到获得一个准确的测量值为止。在Go 1.5中, test 子命令拥有一个 -test.count 标志,它可以让用户指定每个测试以及基准测试的运行次数,该标志的默认值为1。

注意,上面的命令既运行了基准测试,也运行了功能测试。如果需要,用户也可以通过运行标志 -run 来忽略功能测试。 -run 标志用于指定需要被执行的功能测试用例,如果用户把一个不存在的功能测试名字用作 -run 标志的参数,那么所有功能测试都将被忽略。比如,如果我们执行以下命令:

go test -run x -bench .

那么由于我们的测试中不存在任何名字为 x 的功能测试用例,因此所有功能测试都不会被运行。在只执行基准测试的情况下, go test 命令将产生以下结果:

PASS
BenchmarkDecode  100000    19714 ns/op
ok  unit_testing 2.150s

虽然检测单个函数的运行速度非常有用,但如果我们能够对比两个函数的运行速度,那么事情无疑会变得更加有意义!回想一下,我们在第7章曾经学过如何用两种不同的方法把JSON数据解封为结构:一种是使用 Decode 函数,另一种则是使用 Unmarshal 函数。因为上面的基准测试已经检测出了 Decode 函数的运行速度,那么接下来就让我们检测一下 Unmarshal 函数的运行速度吧。但是在进行基准测试之前,我们需要像代码清单8-6展示的那样,将解封操作的代码重构到 main.go文件unmarshal 函数中。

代码清单8-6 解封JSON数据的函数

func unmarshal(filename string) (post Post, err error) {
 jsonFile, err := os.Open(filename)
 if err != nil {
  fmt.Println("Error opening JSON file:", err)
  return
 }
 defer jsonFile.Close()
 jsonData, err := ioutil.ReadAll(jsonFile)
 if err != nil {
  fmt.Println("Error reading JSON data:", err)
  return
 }
 json.Unmarshal(jsonData, &post)
 return
}

之后,我们还需要在基准测试文件 bench_test.go 中添加代码清单8-7所示的基准测试用例,以便对 u nmarshal函数进行基准测试。

代码清单8-7 对 u nmarshal函数进行基准测试

func BenchmarkUnmarshal(b *testing.B) {
 for i := 0; i < b.N; i++ {
  unmarshal("post.json")
 }
}

一切准备就绪之后,再次运行基准测试命令,我们将得到以下结果:

PASS
BenchmarkDecode  100000    19577 ns/op
BenchmarkUnmarshal   50000    24532 ns/op
ok  unit_testing 3.628s

从上述结果可以看到, Decode 函数每次执行需要耗费0.019577 ms,而 Unmarshal 函数每次执行需要耗费0.024532 ms,这说明 Unmarshal 函数比 Decode 函数慢了大约25%。

results matching ""

    No results matching ""